package logs

import (
	"encoding/json"
	"fmt"
	"github.com/go-logr/logr"
	"github.com/go-logr/zapr"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"net/http"
	"os"
)

var (
	Logger      *zap.Logger
	LoggerSTD   logr.Logger
	JsonEncoder zapcore.Encoder
	AtomLevel   = zap.NewAtomicLevel()
	LogLevel    = os.Getenv("LOG_LEVEL")
)

type reverseLogsLevels int

const (
	DebugLevel  = reverseLogsLevels(zap.DebugLevel)
	InfoLevel   = reverseLogsLevels(zap.InfoLevel)
	WarnLevel   = reverseLogsLevels(-1 * zap.WarnLevel)
	ErrorLevel  = reverseLogsLevels(-1 * zap.ErrorLevel)
	DPanicLevel = reverseLogsLevels(-1 * zap.DPanicLevel)
	PanicLevel  = reverseLogsLevels(-1 * zap.PanicLevel)
	FatalLevel  = reverseLogsLevels(-1 * zap.FatalLevel)
)

type outputLogs int

const (
	StdOut outputLogs = iota
	MultiOut
)

func parseAndSetReverseLevel(level string) error {

	switch level {
	case "debug", "DEBUG":
		AtomLevel.SetLevel(zapcore.Level(DebugLevel))
	case "info", "INFO", "":
		AtomLevel.SetLevel(zapcore.Level(InfoLevel))
	case "warn", "WARN":
		AtomLevel.SetLevel(zapcore.Level(WarnLevel))
	case "error", "ERROR":
		AtomLevel.SetLevel(zapcore.Level(ErrorLevel))
	case "dpanic", "DPANIC":
		AtomLevel.SetLevel(zapcore.Level(DPanicLevel))
	case "panic", "PANIC":
		AtomLevel.SetLevel(zapcore.Level(PanicLevel))
	case "fatal", "FATAL":
		AtomLevel.SetLevel(zapcore.Level(FatalLevel))
	default:
		AtomLevel.SetLevel(zapcore.Level(FatalLevel))
		return fmt.Errorf("unrecognized level: %q", level)
	}
	return nil
}

type LogOptions struct {
	Out          outputLogs
	LvlFluentBit zapcore.Level
	LvlConsole   zapcore.Level
}

func Log(logsOutputOptions LogOptions, keysAndValues ...interface{}) {
	data, _ := json.Marshal(keysAndValues)
	Logger.Log(logsOutputOptions.LvlConsole, string(data))
}

func Fatal(keysAndValues ...interface{}) {
	Log(LogOptions{Out: StdOut, LvlConsole: zap.FatalLevel}, keysAndValues...)
}

func Error(keysAndValues ...interface{}) {
	Log(LogOptions{Out: StdOut, LvlConsole: zap.ErrorLevel}, keysAndValues...)
}

func Errorf(message string, keysAndValues ...interface{}) {
	Log(LogOptions{Out: StdOut, LvlConsole: zap.ErrorLevel}, fmt.Sprintf(message, keysAndValues...))
}

func Warn(keysAndValues ...interface{}) {
	Log(LogOptions{Out: StdOut, LvlConsole: zap.WarnLevel}, keysAndValues...)
}

func Info(keysAndValues ...interface{}) {
	Log(LogOptions{Out: StdOut, LvlConsole: zap.InfoLevel}, keysAndValues...)
}

func Infof(message string, keysAndValues ...interface{}) {
	Log(LogOptions{Out: StdOut, LvlConsole: zap.InfoLevel}, fmt.Sprintf(message, keysAndValues...))
}

func Debug(keysAndValues ...interface{}) {
	Log(LogOptions{Out: StdOut, LvlConsole: zap.DebugLevel}, keysAndValues...)
}

func Debugf(message string, keysAndValues ...interface{}) {
	Log(LogOptions{Out: StdOut, LvlConsole: zap.DebugLevel}, fmt.Sprintf(message, keysAndValues...))
}

func LoggersInit() {
	var err error

	encoderCfg := zap.NewDevelopmentEncoderConfig()
	encoderCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
	encoderCfg.EncodeTime = zapcore.RFC3339TimeEncoder
	JsonEncoder = zapcore.NewConsoleEncoder(encoderCfg)

	Logger = zap.New(zapcore.NewCore(
		JsonEncoder,
		zapcore.WriteSyncer(os.Stdout),
		AtomLevel))

	LoggerSTD = zapr.NewLogger(Logger)

	defer Logger.Sync()

	if err = parseAndSetReverseLevel(LogLevel); err != nil {
		Log(LogOptions{Out: StdOut, LvlConsole: zap.FatalLevel}, fmt.Sprintf(err.Error()))
		os.Exit(1)
	}
}

func ChangeDebugLevelServerStart(addr string, serveMux *http.ServeMux) {
	Infof("Starting logger on addr %s", addr)
	Infof("See log level by path %s", fmt.Sprintf("http://localhost%s/logging", addr))
	serveMux.Handle("/logging", AtomLevel)
}

func IsDebugMode() bool {
	return AtomLevel.Level() == zap.DebugLevel
}
